package com.uuah.server.exception;

import java.beans.ExceptionListener;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.uuah.api.lifecycle.IDisposable;
import com.uuah.api.lifecycle.Initialisable;
import com.uuah.exception.lifecycle.InitialisationException;
import com.uuah.exception.lifecycle.LifecycleException;
import com.uuah.server.UuahEvent;
import com.uuah.server.UuahServer;
import com.uuah.server.api.IUuahContext;
import com.uuah.server.api.IUuahEvent;
import com.uuah.server.api.IUuahEventContext;
import com.uuah.server.api.IUuahMessage;
import com.uuah.server.api.context.IUuahContextAware;
import com.uuah.server.api.endpoint.IEndpointURI;
import com.uuah.server.api.endpoint.IUuahEndpoint;
import com.uuah.server.api.util.IStreamCloserService;
import com.uuah.server.config.Constants;
import com.uuah.server.config.i18n.ServerMessages;
import com.uuah.server.context.notification.ExceptionNotification;
import com.uuah.server.exception.channel.RoutingException;
import com.uuah.server.exception.endpoint.InvalidEndpointTypeException;
import com.uuah.server.message.ExceptionMessage;
import com.uuah.server.message.UuahMessage;
import com.uuah.server.session.UuahSession;
import com.uuah.server.session.UuahSessionHandler;
import com.uuah.server.transport.NullPayload;
import com.uuah.server.transport.RequestContextHelper;
import com.uuah.utils.CollectionUtils;

/**
 *
 * TODO comment ...
 *
 * <p>
 * update record:updator，update time，update content and version
 * </p>
 *
 * @author <a href="jonny_quan@hotmail.com">jonny</a>
 * @date 2009 2009-3-27 下午01:45:03
 * @version 1.0.0
 */
public abstract class AbstractExceptionListener implements ExceptionListener,
		Initialisable, IDisposable, IUuahContextAware {
	protected transient Logger logger = LoggerFactory.getLogger(getClass());

	protected List endpoints = new CopyOnWriteArrayList();

	protected AtomicBoolean initialised = new AtomicBoolean(false);

	protected IUuahContext uuahContext;

	protected boolean enableNotifications = true;

	public void setUuahContext(IUuahContext context) {
		this.uuahContext = context;
	}

	public List getEndpoints() {
		return endpoints;
	}

	public void setEndpoints(List endpoints) {
		if (endpoints != null) {
			this.endpoints.clear();
			for (Iterator it = endpoints.iterator(); it.hasNext();) {
				IUuahEndpoint endpoint = (IUuahEndpoint) it.next();
				if (!(endpoint instanceof IUuahEndpoint)) {
					throw new InvalidEndpointTypeException(ServerMessages
							.exceptionListenerMustUseRequestEndpoint(this,
									endpoint));
				}
			}
			this.endpoints.addAll(endpoints);
		} else {
			throw new IllegalArgumentException("List of endpoints = null");
		}
	}

	public void addEndpoint(IUuahEndpoint endpoint) {
		if (endpoint != null) {
			endpoints.add(endpoint);
		}
	}

	public boolean removeEndpoint(IUuahEndpoint endpoint) {
		return endpoints.remove(endpoint);
	}

	public void exceptionThrown(Exception e) {
		if (enableNotifications) {
			fireNotification(new ExceptionNotification(e));
		}

		logException(e);

		Throwable t = getExceptionType(e, RoutingException.class);
		if (t != null) {
			RoutingException re = (RoutingException) t;
			handleRoutingException(re.getUuahMessage(), re.getEndpoint(), e);
			return;
		}

		t = getExceptionType(e, MessagingException.class);
		if (t != null) {
			MessagingException me = (MessagingException) t;
			handleMessagingException(me.getUuahMessage(), e);
			return;
		}

		t = getExceptionType(e, LifecycleException.class);
		if (t != null) {
			LifecycleException le = (LifecycleException) t;
			handleLifecycleException(le.getComponent(), e);
			if (RequestContextHelper.getEventContext() != null) {
				handleMessagingException(RequestContextHelper.getEventContext()
						.getMessage(), e);
			} else {
				if (logger.isInfoEnabled()) {
					logger
							.info("There is no current event available, routing Null message with the exception");
				}
				handleMessagingException(new UuahMessage(NullPayload
						.getInstance()), e);
			}
			return;
		}

		handleStandardException(e);
	}

	protected Throwable getExceptionType(Throwable t, Class exceptionType) {
		while (t != null) {
			if (exceptionType.isAssignableFrom(t.getClass())) {
				return t;
			}

			t = t.getCause();
		}

		return null;
	}

	public final synchronized void initialise() throws InitialisationException {
		if (!initialised.get()) {
			doInitialise(uuahContext);
			initialised.set(true);
		}
	}

	protected void doInitialise(IUuahContext uuahContext)
			throws InitialisationException {
		if (logger.isInfoEnabled()) {
			logger.info("Initialising exception listener: " + toString());
		}
	}

	protected void routeException(IUuahMessage message,
			IUuahEndpoint failedEndpoint, Throwable t) {
		List endpoints = getEndpoints(t);
		if (CollectionUtils.isNotEmpty(endpoints)) {
			try {
				logger.error("Message being processed is: "
						+ (message == null ? "null" : message.toString()));
				IUuahEventContext ctx = RequestContextHelper.getEventContext();
				String component = "Unknown";
				IEndpointURI endpointUri = null;
				if (ctx != null) {
					if (ctx.getService() != null) {
						component = ctx.getService().getName();
					}
					endpointUri = ctx.getEndpointURI();
				} else if (failedEndpoint != null) {
					endpointUri = failedEndpoint.getEndpointURI();
				}
				ExceptionMessage msg;
				msg = new ExceptionMessage(getErrorMessagePayload(message), t,
						component, endpointUri);

				IUuahMessage exceptionMessage;
				if (ctx == null) {
					exceptionMessage = new UuahMessage(msg);
				} else {
					exceptionMessage = new UuahMessage(msg, ctx.getMessage());
				}

				for (int i = 0; i < endpoints.size(); i++) {
					IUuahEndpoint endpoint = (IUuahEndpoint) endpoints.get(i);
					IUuahEvent exceptionEvent = new UuahEvent(exceptionMessage,
							endpoint, new UuahSession(exceptionMessage,
									new UuahSessionHandler(), uuahContext), true);
					exceptionEvent = RequestContextHelper
							.setEvent(exceptionEvent);
					endpoint.send(exceptionEvent);

					if (logger.isDebugEnabled()) {
						logger
								.debug("routed Exception message via "
										+ endpoint);
					}
				}
			} catch (AbstractUuahServerException e) {
				logFatal(message, e);
				closeStream(message);
			}
		} else {
			closeStream(message);
		}
	}

	protected void closeStream(IUuahMessage message) {
		if (uuahContext != null
				&& message != null
				&& uuahContext.getRegistry().lookupObject(
						Constants.OBJECT_UUAH_STREAM_CLOSER_SERVICE) != null) {
			((IStreamCloserService) uuahContext.getRegistry().lookupObject(
					Constants.OBJECT_UUAH_STREAM_CLOSER_SERVICE))
					.closeStream(message.getPayload());
		}
	}

	protected Object getErrorMessagePayload(IUuahMessage message) {
		try {
			return message.getPayloadAsString();
		} catch (Exception e) {
			logException(e);
			if (logger.isInfoEnabled()) {
				logger
						.info("Failed to read message payload as string, using raw payload");
			}
			return message.getPayload();
		}
	}

	protected List getEndpoints(Throwable t) {
		if (!endpoints.isEmpty()) {
			return endpoints;
		} else {
			return null;
		}
	}

	protected void logException(Throwable t) {
		logger.error("Caught exception in Exception Strategy: "
				+ t.getMessage(), t);
		UuahServer.getServerStats().incExecutionError();
	}

	protected void logFatal(IUuahMessage message, Throwable t) {
		logger
				.warn(
						"Failed to dispatch message to error queue after it failed to process.  This may cause message loss."
								+ (message == null ? ""
										: "Logging Message here: \n"
												+ message.toString()), t);
		UuahServer.getServerStats().incFatalError();
	}

	public boolean isInitialised() {
		return initialised.get();
	}

	public void dispose() {
		// Template method
	}

	protected void fireNotification(ExceptionNotification notification) {
		if (uuahContext != null) {
			uuahContext.fireNotification(notification);
		} else if (logger.isDebugEnabled()) {
			logger
					.debug("UuahContext is not yet available for firing notifications, ignoring event: "
							+ notification);
		}
	}

	public boolean isEnableNotifications() {
		return enableNotifications;
	}

	public void setEnableNotifications(boolean enableNotifications) {
		this.enableNotifications = enableNotifications;
	}

	public abstract void handleMessagingException(IUuahMessage message,
			Throwable e);

	public abstract void handleRoutingException(IUuahMessage message,
			IUuahEndpoint endpoint, Throwable e);

	public abstract void handleLifecycleException(Object component, Throwable e);

	public abstract void handleStandardException(Throwable e);

}
